using Crux.Core.Data;

namespace Crux.Core.Dapper.SqlGenerator.Filters;
/// <summary>
/// 
/// </summary>
public class FilterData
{
    /// <summary>
    /// The query select settings
    /// </summary>
    public SelectInfo? SelectInfo { get; set; }

    /// <summary>
    /// The query order settings
    /// </summary>
    public OrderInfo? OrderInfo { get; set; }

    /// <summary>
    /// The query group settings
    /// </summary>
    public GroupInfo? GroupInfo { get; set; }

    /// <summary>
    /// The query limits settings
    /// </summary>
    public LimitInfo? LimitInfo { get; set; }

    /// <summary>
    /// Specify if the query is ordered
    /// </summary>
    public bool Ordered { get; set; }
    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public FilterData ByOrder<T>(OrderExpression<T> orderExpression) where T : class
    {
        if (orderExpression is null)
        {
            throw new ArgumentNullException(nameof(orderExpression));
        }
        if (orderExpression.Expression is null)
        {
            throw new ArgumentNullException(nameof(orderExpression.Expression));
        }

        var expr = orderExpression.Expression;

        var order = new OrderInfo
        {
            Permanent = false
        };

        this.OrderInfo = order;

        var type = typeof(T);

        switch (expr.Body.NodeType)
        {
            case ExpressionType.Convert:
                {
                    if (expr.Body is UnaryExpression { Operand: MemberExpression expression })
                    {
                        order.Columns = new Dictionary<string, OrderInfo.SortDirection> { {GetProperty(expression, type),orderExpression.Symbol switch
                        {
                            OrderSymbol.Desc => OrderInfo.SortDirection.DESC,
                            _ => OrderInfo.SortDirection.ASC
                        }}};
                    }

                    break;
                }
            case ExpressionType.MemberAccess:
                order.Columns = new Dictionary<string, OrderInfo.SortDirection> { {GetProperty(expr.Body, type),orderExpression.Symbol switch
                {
                    OrderSymbol.Desc => OrderInfo.SortDirection.DESC,
                    _ => OrderInfo.SortDirection.ASC
                }}};
                break;
            default:
                if (expr.Body is NewExpression newExpression)
                {
                    var arguments = newExpression.Arguments;
                    if (arguments is not null)
                    {
                        var propertyNames = arguments.Select(expression => GetProperty(expression, type)).ToList();
                        order.Columns = new Dictionary<string, OrderInfo.SortDirection>();

                        foreach (var name in propertyNames)
                        {
                            order.Columns.Add(name, orderExpression.Symbol switch
                            {
                                OrderSymbol.Desc => OrderInfo.SortDirection.DESC,
                                _ => OrderInfo.SortDirection.ASC
                            });
                        }
                    }
                }

                break;
        }

        return this;
    }
    /// <summary>
    /// 
    /// </summary>
    /// <param name="limit"></param>
    /// <param name="offset"></param>
    /// <returns></returns>
    public FilterData ByLimit(uint limit = 0, uint offset = 0)
    {
        if (limit > 0 || offset > 0)
        {
            this.LimitInfo = new LimitInfo
            {
                Limit = limit,
                Offset = offset
            };
        }
        return this;
    }
    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="TEntity"></typeparam>
    public FilterData BySelect<TEntity>(Expression<Func<TEntity, object>> expr) where TEntity : class
    {
        this.SelectInfo = new SelectInfo();

        var type = typeof(TEntity);
        if (expr.Body.NodeType == ExpressionType.Lambda)
        {
            if (expr.Body is UnaryExpression { Operand: MemberExpression expression })
            {
                var prop = GetProperty(expression, type, true);
                this.SelectInfo.Columns.Add(prop);
            }
        }
        else if (expr.Body is NewExpression newExpression)
        {
            var cols = newExpression.Arguments;
            if (cols is not null)
            {
                foreach (var expression in cols)
                {
                    var prop = GetProperty(expression, type, true);
                    if (string.IsNullOrEmpty(prop))
                        continue;

                    this.SelectInfo.Columns.Add(prop);
                }
            }
        }

        return this;
    }
    private static string GetProperty(Expression expression, Type type, bool select = false)
    {
        var field = (MemberExpression)expression;

        var prop = type.GetProperty(field.Member.Name);

        return GetProperty(type, prop, select);
    }

    private static string GetProperty(Type type, PropertyInfo? prop, bool select = false)
    {
        var declaringType = type.GetTypeInfo();
        var tableAttribute = declaringType.GetCustomAttribute<TableAttribute>();
        var tableName = tableAttribute is not null ? tableAttribute.Name : declaringType.Name;

        if (prop is null || prop.GetCustomAttribute<NotMappedAttribute>() is not null)
            return string.Empty;

        var name = prop.GetCustomAttribute<ColumnAttribute>()?.Name ?? prop.Name;
        if (select)
        {
            return $"{tableName}.{name} AS {prop.Name}";
        }
        else
        {
            return $"{tableName}.{name}";
        }
    }

}